using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace BardsTaleViewer {
  public class Picture_PC : IPicture {
    public Bitmap _bitmap;
    public IAnims _anims;

    public Picture_PC (Bitmap bitmap, IAnims anims) {
      _bitmap = bitmap;
      _anims = anims;
    }

    public static Picture_PC Load (string filename, int pictureIndex) {
      string filenamePic = "BTCS_PC/" + filename + ".pic";
      if (!File.Exists (filenamePic))
        return (null);

      // PAC
      string filenamePac = "BTCS_PC/" + filename + ".pac";
      byte [] filePac = Unpack (filenamePac);
      //filePac.WriteToFile ("bard 4 PC palette.dmp");
      //if (filePac.Length != (256 * 3))
      //  throw new Exception ();

      int offsetPac = 0;
      Color [] colors = new Color [256];
      for (int i = 0; i < 256; i++) {
        int r = filePac.ReadByte (ref offsetPac) << 2;
        int g = filePac.ReadByte (ref offsetPac) << 2;
        int b = filePac.ReadByte (ref offsetPac) << 2;
        colors [i] = Color.FromArgb (r, g, b);
      }

      // PIC
      byte [] a = Unpack (filenamePic);
      //a.WriteToFile ("bard 4 PC pic1.dmp");
      //if (pictureIndex <= 37) {
      bool noAnimsInPic = true;
      if (a.ReadWord (0) == 0x00FF)
        noAnimsInPic = false;

      Bitmap bitmapBase = null;
      byte [] dataBase = null;
      List<int> ids1 = null;
      List<int> ids2 = null;
      List<int> sizes = null;
      List<byte []> picturesData = null;
      List<Bitmap> picturesBitmap = null;
      if (!noAnimsInPic) {
        ids1 = new List<int> ();
        ids2 = new List<int> ();
        sizes = new List<int> ();
        picturesData = new List<byte []> ();
        picturesBitmap = new List<Bitmap> ();

        int offsetSrc = 0;
        for (; ; ) {
          if (offsetSrc == a.Length)
            break;
          int id1 = a.ReadByte (ref offsetSrc);
          int id2 = a.ReadByte (ref offsetSrc);
          int size = a.ReadWordLE (ref offsetSrc);
          byte [] pictureData = UnRLE (a, offsetSrc);
          //if (pictureData.Length != (112 * 88))
          //  throw new Exception ();
          Bitmap pictureBitmap = CreateBitmap (pictureData, colors);
          offsetSrc += size;
          ids1.Add (id1);
          ids2.Add (id2);
          sizes.Add (size);
          picturesData.Add (pictureData);
          picturesBitmap.Add (pictureBitmap);
          //pictureData.WriteToFile (string.Format ("bard 4 PC pics {0} {1} {2:X4}.dmp", pictureIndex.ToString ().PadLeft (2, '0'), (picturesData.Count - 1).ToString ().PadLeft (2, '0'), id));
          //pictureBitmap.Save (string.Format ("bard4 PC pics {0} {1} {2:X2} {3:X2}.png", pictureIndex.ToString ().PadLeft (2, '0'), (picturesBitmap.Count - 1).ToString ().PadLeft (2, '0'), id1, id2), ImageFormat.Png);
        }

        bitmapBase = picturesBitmap [0];
        dataBase = picturesData [0];
      }
      else {
        //if (a.Length != (112 * 88))
        //  throw new Exception ();
        bitmapBase = CreateBitmap (a, colors);
        dataBase = a;
      }

      //picture.WriteToFile ("bard 4 PC pic2.dmp");


      //SavePalette ("bard 4 PC palette.png", colors);

      //bitmap.Save ("bard 4 PC.png", ImageFormat.Png);

      string filenameScr = "BTCS_PC/" + filename + ".scr";
      bool noAnimsInScr = false;
      SCR_anims scrAnims = null;
      try {
        scrAnims = new SCR_anims (filenameScr);
      }
      catch (FileNotFoundException) {
        noAnimsInScr = true;
      }

      if (noAnimsInPic && !noAnimsInScr)
        Console.WriteLine ("UNNEEDED SCR FILE");

      if (!noAnimsInPic && noAnimsInScr)
        throw new Exception ();

      if (noAnimsInPic)
        return (new Picture_PC (bitmapBase, null));

      // SCR
      Anims_PC_Bard4 anims = new Anims_PC_Bard4 ();

      for (int animIndex = 0; animIndex < scrAnims._animsElements.Count; animIndex++) {
        List<SCR_element> animElements = scrAnims._animsElements [animIndex];

        bool delayBefore;
        SCR_element animElementPrev = scrAnims.getAnimElement (animIndex, - 1);
        if (animElements [0]._frame == 0)
          delayBefore = true;
        else if (animElementPrev._frame == 0)
          delayBefore = false;
        else
          throw new Exception ();
        Anim_PC_Bard4 anim = new Anim_PC_Bard4 (delayBefore);

        for (int frameIndex = 0; frameIndex < animElements.Count; frameIndex++) {
          SCR_element animElement = animElements [frameIndex];
          SCR_element animElementOther = null;
          if (delayBefore)
            animElementOther = scrAnims.getAnimElement (animIndex, frameIndex + 1);
          else
            animElementOther = scrAnims.getAnimElement (animIndex, frameIndex - 1);

          int pictureIndex2 = GetPicture (animElement._frame, animElementOther._frame, ids1, ids2);
          //if (animIndex == 2)
          //  if (delayBefore)
          //    Console.WriteLine ("{0} -> {1} = {2}", animElement._frame, animElementOther._frame, pictureIndex2);
          //  else
          //    Console.WriteLine ("{0} -> {1} = {2}", animElementOther._frame, animElement._frame, pictureIndex2);

          //Bitmap bitmap = null;
          //if (animElement._frame >= picturesBitmap.Count) {
          //  bitmap = new Bitmap (112, 88);
          //  Graphics g = Graphics.FromImage (bitmap);
          //  g.Clear (Color.Red);
          //  Console.WriteLine ("ERROR IN ANIM");
          //}
          //else
          //bitmap = picturesBitmap [pictureIndex2];
          byte [] pictureData = null;
          if (pictureIndex2 == -1) {
            pictureData = new byte [112 * 88];
          }
          else
            pictureData = picturesData [pictureIndex2];
          int delay = animElement._delay;

          AnimFrame_PC_Bard4 frame = new AnimFrame_PC_Bard4 (animIndex, frameIndex, delay, pictureData, pictureIndex2);
          anim.AddFrame (frame);
        }
        anims.AddAnim (anim);
      }

      anims.CreateBitmaps (bitmapBase, colors, dataBase);
      return (new Picture_PC (bitmapBase, anims));
    }

    public static Bitmap CreateBitmap (byte [] data, Color [] colors) {
      Bitmap bitmap = new Bitmap (112, 88);
      for (int y = 0; y < 88; y++)
        for (int x = 0; x < 112; x++)
          bitmap.SetPixel (x, y, colors [data [y * 112 + x]]);
      return (bitmap);
    }

    public static void SavePalette (string filename, Color [] colors) {
      int _size = colors.Length;
      Size cellSize = new Size (16, 7);
      Bitmap bitmap = new Bitmap (16 * (cellSize.Width + 1) + 1, ((_size - 1) / 16 + 1) * (cellSize.Height + 1) + 1, PixelFormat.Format32bppPArgb);
      Graphics graphics = Graphics.FromImage (bitmap);
      graphics.Clear (Color.Black);
      Font font = new Font (FontFamily.GenericSansSerif, 6.5f);
      for (int y = 0; y < 16; y++) {
        for (int x = 0; x < 16; x++) {
          if ((y * 16 + x) >= _size) {
            y = 16;
            break;
          }
          int index = y * 16 + x;
          Point xy = new Point (x * (cellSize.Width + 1) + 1, y * (cellSize.Height + 1) + 1);
          graphics.FillRectangle (new SolidBrush (colors [y * 16 + x]), xy.X, xy.Y, cellSize.Width, cellSize.Height);
          //graphics.DrawStringOutlinedCentered (index.ToString (), font, Brushes.White, Brushes.Black, xy.X + cellSize.Width / 2, xy.Y + (cellSize.Height + 1) / 2);
        }
      }
      bitmap.Save (filename, ImageFormat.Png);
    }

    public static byte [] Unpack (string filename) {
      byte [] file = ExtensionByte.CreateFromFile (filename);
      byte [] data = new byte [4096];
      byte [] a = new byte [0xFFFF];
      int where = 0x0FEE;
      int length = 0;

      //int size = 1;

      int offsetFile = 0;
      int offsetA = 0;
      //for (int i = number * size; i > 0; ) {
      for (int i = int.MaxValue; i > 0; ) {
        if (0 == length) {
          if (offsetFile >= file.Length)
            goto Stop;
          byte format = (byte) file.ReadByte (ref offsetFile);
          for (int k = 0; k < 8; k++, format >>= 1) {
            if ((format & 0x01) == 1) {
              if (offsetFile >= file.Length)
                goto Stop;
              data [(where + length) & 0x0FFF] = (byte) file.ReadByte (ref offsetFile);
              length++;
            }
            else {
              byte [] tmpData = new byte [2];
              if (offsetFile >= file.Length)
                goto Stop;
              tmpData [0] = (byte) file.ReadByte (ref offsetFile);
              if (offsetFile >= file.Length)
                goto Stop;
              tmpData [1] = (byte) file.ReadByte (ref offsetFile);
              int location = tmpData [0] + ((tmpData [1] & 0xF0) << 4);
              tmpData [1] = (byte) ((tmpData [1] & 0x0F) + 3);
              for (int count = 0; count < tmpData [1]; count++, length++) {
                data [(where + length) & 0x0FFF] = data [(location + count) & 0x0FFF];
              }
            }
          }
        }
        int amount = ((i > length) ? length : i);
        if ((where + amount) > 4096) {
          //memcpy (a, data + where, 4096 - where);
          //memcpy (a + (4096 - where), data, (where + amount) & 0x0FFF);
          data.CopyTo (where, a, offsetA, 4096 - where);
          data.CopyTo (0, a, offsetA + 4096 - where, (where + amount) & 0x0FFF);
        }
        else
          //memcpy (a, data + where, amount);
          data.CopyTo (where, a, offsetA, amount);

        where = (where + amount) & 0x0FFF;
        length -= amount;
        i -= amount;
        offsetA += amount;
      }
Stop:
      return (a.Extract (0, offsetA + length));
    }

    public static byte [] UnRLE (byte [] src, int offsetSrc) {
      byte [] dst = new byte [112 * 88];
      int offsetDst = 0;

      for (; ; ) {
        if ((offsetSrc == src.Length) ||
            (offsetDst == dst.Length)) {
          //offsetDst--;
          break;
        }
        int b1 = src.ReadByte (ref offsetSrc);
        if (b1 == 0x7F)
          break;
        if (b1 >= 0x80) {
          int val = src.ReadByte (ref offsetSrc);
          int nbToWrite = (b1 & 0x7F) + 2;
          //if (val == 0) {
          //  // write n zero
          //  offsetDst += nbToWrite;
          //}
          //else {
          for (int i = 0; i < nbToWrite; i++)
            dst.WriteByte (offsetDst++, val);
          //}
        }
        else
          dst.WriteByte (offsetDst++, b1);
      }

      //return (dst.Extract (0, offsetDst));
      return (dst);
    }

    public Bitmap Bitmap {
      get {
        return (_bitmap);
      }
    }

    public List<IAnim> Anims {
      get {
        if (_anims == null)
          return (null);
        return (_anims.Anims);
      }
    }

    public static int GetPicture (int id1, int id2, List<int> ids1, List<int> ids2) {
      for (int i = 0; i < ids1.Count; i++)
        if (((ids1 [i] == id1) && (ids2 [i] == id2)) ||
            ((ids1 [i] == id2) && (ids2 [i] == id1)))
          return (i);
      if (id1 != id2)
        throw new Exception ();
      return (-1);
    }
  }
}
